package com.hdu.stream;


import java.util.*;
import java.util.function.*;

public class Stream<T> {

    private final Collection<T> collection;

    private Stream(Collection<T> collection) {
        this.collection = collection;
    }

    public static <T> Stream<T> of(Collection<T> collection) {
        return new Stream<>(collection);
    }

    @SuppressWarnings("all")
    public Optional<T> findAny() {
        List<T> newCollection = new ArrayList<>(collection);
        Collections.shuffle(newCollection);
        T result = null;
        for (T t : newCollection) {
            result = t;
            if (result != null) {
                return Optional.of(result);
            }
        }
        return Optional.empty();
    }


    public int sum(Function<T, Integer> function) {
        int sum = 0;
        for (T t : collection) {
            sum += function.apply(t);
        }
        return sum;
    }

    public T min(Comparator<T> comparator) {
        List<T> tempList = new ArrayList<>(collection);
        T min = tempList.get(0);
        for (T t : tempList) {
            if (comparator.compare(t, min) < 0) {
                min = t;
            }
        }
        return min;
    }

    public T max(Comparator<T> comparator) {
        List<T> tempList = new ArrayList<>(collection);
        T max = tempList.get(0);
        for (T t : tempList) {
            if (comparator.compare(t, max) > 0) {
                max = t;
            }
        }
        return max;
    }

    public double avg(Function<T, Integer> function) {
        double avg = 0;
        int count = this.count();
        for (T t : collection) {
            avg += function.apply(t)* 1.0/ count;
        }
        return avg;
    }

    public int count() {
        return collection.size();
    }

    public Stream<T> distinct() {
        Set<T> set = new HashSet<>(collection);
        return of(set);
    }

    public boolean anyMatch(Predicate<T> predicate) {
        for (T t : collection) {
            if (predicate.test(t)) {
                return true;
            }
        }
        return false;
    }


    public boolean allMatch(Predicate<T> predicate) {
        for (T t : collection) {
            if (!predicate.test(t)) {
                return false;
            }
        }
        return true;
    }

    public boolean noneMatch(Predicate<T> predicate) {
        for (T t : collection) {
            if (predicate.test(t)) {
                return false;
            }
        }
        return true;
    }

    public Optional<T> findFirst() {
        T res;
        for (T t : collection) {
            res = t;
            if (res != null) {
                return Optional.of(res);
            }
        }
        return Optional.empty();
    }

    public <K> Map<K, List<T>> groupBy(Function<T, K> keyMapper) {
        Map<K, List<T>> map = new HashMap<>();
        for (T t : collection) {
            if (map.containsKey(keyMapper.apply(t))) {
                map.get(keyMapper.apply(t)).add(t);
            } else {
                map.put(keyMapper.apply(t), new ArrayList<>(Collections.singletonList(t)));
            }
        }
        return map;
    }

    public Stream<T> sort(Comparator<T> comparator) {
        List<T> newCollection = new ArrayList<>(collection);
        newCollection.sort(comparator);
        return of(newCollection);
    }

    public Stream<T> filter(Predicate<T> predicate) {
        List<T> newCollection = new ArrayList<>();
        for (T t : collection) {
            if (predicate.test(t)) {
                newCollection.add(t);
            }
        }
        return of(newCollection);
    }

    public <V> Stream<V> map(Function<T, V> function) {
        List<V> newCollection = new ArrayList<>();
        for (T t : collection) {
            newCollection.add(function.apply(t));
        }
        return of(newCollection);
    }


    public void forEach(Consumer<T> consumer)
    {
        for (T t : collection) {
            consumer.accept(t);
        }
    }

    public <K, V> Map<K, V> collectToMap(Function<T, K> keyMapper, Function<T, V> valueMapper) {
        Map<K, V> map = new HashMap<>();
        for (T t : collection) {
            map.put(keyMapper.apply(t), valueMapper.apply(t));
        }
        return map;
    }

    public <C> C collect(Supplier<C> supplier, BiConsumer<C, T> biConsumer) {
        C c = supplier.get();
        for (T t : collection) {
            biConsumer.accept(c, t);
        }
        return c;
    }

    public T reduce(T init, BinaryOperator<T> binaryOperator) {
        T result = init;
        for (T t : collection) {
            result = binaryOperator.apply(result, t);
        }
        return result;
    }

}
